package mylittleinvestigations.HeightMaps;

import java.io.IOException;
import mylittleinvestigations.CaseInformation.CaseXmlReader;
import mylittleinvestigations.CaseInformation.CaseXmlWriter;
import mylittleinvestigations.Vector2;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Polygon;

public abstract class HeightMap
{
    protected Vector2 directionVector;
    protected Polygon boundingPolygon;
    
    public boolean IsPointInBoundingPolygon(Vector2 point) { return this.boundingPolygon.contains((int)point.getX(), (int)point.getY()); }
    public void setDirectionVector(Vector2 directionVector) { this.directionVector = directionVector.normalize(); }
    public void setBoundingPolygon(Polygon boundingPolygon) { this.boundingPolygon = boundingPolygon; }
    
    public abstract int GetHeightAtPoint(Vector2 point);
    protected abstract int GetHighestHeight();
    
    public Vector2 GetBasePointOffsetFromHeightenedPoint(Vector2 point)
    {
        // TODO: Idea I had for this: Rather than try to directly calculate the base point from the heightened point,
        // we get a vector perpendicular to the direction vector.  Then we travel both forwards and in reverse down this
        // perpendicular vector, checking the heightened point corresponding to each point until we either find a point
        // whose heightened point is equal (within a certain tolerance) to our original point,
        // or until we fall off the map.  This makes it so we don't have to calculate the reverse
        // of the heightening operation, which is a very hard thing to do.
        //
        // TODO: Actually, maybe instead of getting that perpendicular vector,
        // we should just always go straight down?  Need to investigate this.
        // Seems like this would make more sense.
        Vector2 basePointOffset = null;
        Rectangle boundingBox = new Rectangle(this.boundingPolygon.getX(), this.boundingPolygon.getY(), this.boundingPolygon.getWidth(), this.boundingPolygon.getHeight());
        
        // First we rule out the situations where this cannot possibly have
        // a base point associated with it: when it's below or to the side
        // of the bounds of the height map.
        if (point.getX() < boundingBox.getX() ||
                point.getX() > boundingBox.getX() + boundingBox.getWidth() ||
                point.getY() > boundingBox.getY() + boundingBox.getHeight())
        {
            return basePointOffset;
        }
        
        int highestHeight = this.GetHighestHeight();
        Vector2 originalPoint = point;
        point = point.Clone();
        
        for (int i = 0; i < highestHeight; i++)
        {
            point.setY(point.getY() + 1);
            
            if (point.getY() > boundingBox.getY() + boundingBox.getHeight())
            {
                break;
            }
            else if (Math.abs(point.getY() - this.GetHeightAtPoint(point) - originalPoint.getY()) <= 1 && this.IsPointInBoundingPolygon(point))
            {
                basePointOffset = point.subtract(originalPoint);
                break;
            }
        }
        
        return basePointOffset;
    }
    
    public abstract void SaveToXml(CaseXmlWriter writer) throws IOException;
    
    protected final void SaveToXmlCore(CaseXmlWriter writer) throws IOException
    {
        writer.StartElement("DirectionVector");
        this.directionVector.SaveToXml(writer);
        writer.EndElement();
        
        writer.StartElement("BoundingPolygon");
        writer.WritePolygonElement(this.boundingPolygon);
        writer.EndElement();
    }
    
    public static HeightMap LoadFromXml(CaseXmlReader reader)
    {
        HeightMap heightMap = null;
        
        if (reader.ElementExists("ParabolicHeightMap"))
        {
            heightMap = new ParabolicHeightMap(reader);
        }
        
        return heightMap;
    }
    
    protected final void LoadFromXmlCore(CaseXmlReader reader)
    {
        reader.StartElement("DirectionVector");
        this.directionVector = new Vector2(reader);
        reader.EndElement();
        
        reader.StartElement("BoundingPolygon");
        this.boundingPolygon = reader.ReadPolygonElement();
        reader.EndElement();
    }
}
